﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Configuration;
using System.Web;

namespace Akronus.TaskScheduler
{
    /// <summary>
    /// Reads the configuration file (web.config) and initializes tasks.
    /// </summary>
    public class TaskService : ITaskService
    {
        #region Fields

        private IDictionary<string, TaskManager> tasksManagers = null;
        private TaskConfiguration taskConfiguration = null;
        private ITaskInfoDataService dataService = null;
        private ITaskLog taskLog = null;

        #endregion

        #region Public Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="TaskService"/> class.
        /// </summary>
        public TaskService(ITaskInfoDataService dataService, ITaskLog taskLog)
        {
            this.dataService = dataService;
            this.taskLog = taskLog;
            this.taskConfiguration = (TaskConfiguration)ConfigurationManager.GetSection("tasks");
            Initialize();
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Starts the tasks.
        /// </summary>
        public void StartTasks()
        {
            foreach (TaskManager task in tasksManagers.Values)
            {
                task.Start();
            }
        }

        /// <summary>
        /// Stops the tasks.
        /// </summary>
        public void StopTasks()
        {
            string taskStatus = String.Empty;
            foreach (TaskManager taskManager in tasksManagers.Values)
            {
                taskManager.Stop();

                taskStatus = String.Empty;
                var taskInfo = dataService.GetTaskInfo(taskManager.Task.Name);
                if (taskInfo != null)
                    taskStatus = taskInfo.Status;
            }
        }

        /// <summary>
        /// Runs the specified task.
        /// </summary>
        /// <param name="taskName">Name of the task.</param>
        public void RunTask(string taskName)
        {
            if (tasksManagers.ContainsKey(taskName))
            {
                var manager = tasksManagers[taskName];
                manager.Run();
            }
            else
            {
                taskLog.Add(string.Format("Can't run the task: '{0}'. Probably it was deleted from web.config or there was an error while its initializing. Check previous logs.", taskName), LogPriority.High);
            }
        }

        /// <summary>
        /// Aborts the specified task.
        /// </summary>
        /// <param name="taskName">Name of the task.</param>
        public void AbortTask(string taskName)
        {
            if (tasksManagers.ContainsKey(taskName))
            {
                var manager = tasksManagers[taskName];
                manager.Abort();
            }
            else
            {
                taskLog.Add(string.Format("Can't run the task: '{0}'. Probably it was deleted from web.config or there was an error while its initializing. Check previous logs.", taskName), LogPriority.High);
            }
        }

        #endregion

        #region Private Methods

        private void Initialize()
        {
            taskLog.Add("Started to initialize the task service", LogPriority.Low);

            tasksManagers = new Dictionary<string, TaskManager>();
            ResetTasksStates(dataService);

            foreach (XmlNode node in taskConfiguration.Tasks)
            {
                if (node.Name == "task")
                {
                    try
                    {
                        XmlAttributeCollection attributes = node.Attributes;

                        string taskName = attributes.GetAttributeValue("name");
                        if (tasksManagers.ContainsKey(taskName))
                            throw new ConfigurationErrorsException("Error in tasks configuration. Task name has to be unique.");

                        ITaskScheduleStrategy taskRunStrategy = null;
                        if (attributes["interval"] != null)
                        {
                            TimeSpan interval = TimeSpan.FromSeconds(double.Parse(attributes["interval"].Value));
                            taskRunStrategy = new IntervalTaskScheduleStrategy(taskName, interval.TotalMilliseconds, dataService);
                        }
                        else if (attributes["time"] != null)
                        {
                            taskRunStrategy = new TimeTaskScheduleStrategy(DateTime.Parse(attributes["time"].Value));
                        }
                        else
                        {
                            throw new ConfigurationErrorsException(string.Format("At least one of the settings 'interval' or 'time' has to be specified for the task: {0}.", taskName));
                        }

                        TaskManager taskManager = new TaskManager(dataService, new TaskTimer(), taskRunStrategy, new ThreadTaskRunner(), taskLog);
                        string typeString = attributes.GetAttributeValue("type");
                        Type type = Type.GetType(typeString, true);
                        if (type == null)
                            throw new ConfigurationErrorsException(string.Format("The type which is specified for the {0} task is not found.", taskName));

                        ITask task = (ITask)Activator.CreateInstance(type);
                        task.ApplicationUrl = GetApplicationPath();
                        task.Name = taskName;
                        task.Configuration = node;
                        task.Initialize();

                        taskManager.Task = task;
                        string enabledString = attributes.GetAttributeValue("enabled");
                        taskManager.TaskType = typeString;
                        taskManager.Enabled = bool.Parse(enabledString);
                        taskManager.AllowRunningTaskWithSameType = taskConfiguration.AllowRunningTaskWithSameType;
                        taskManager.ConfigurationNode = node;
                        tasksManagers[taskName] = taskManager;

                        var taskInfo = dataService.GetTaskInfo(taskName);
                        if (taskInfo == null)
                        {
                            var item = new TaskInfo()
                            {
                                Name = taskName,
                                LastRunTime = DateTime.Now,
                                Status = string.Empty,
                                Type = typeString
                            };
                            dataService.SaveTaskInfo(item);
                        }
                    }
                    catch (Exception e)
                    {
                        taskLog.Add(string.Format("Tasks are not configured well in the web.config. The following exception was thrown: {0} ", e.ToString()), LogPriority.High);
                    }
                }
            }
        }

        private void ResetTasksStates(ITaskInfoDataService dataService)
        {
            foreach (var item in dataService.GetTaskInfos())
            {
                if (item.Status == TaskInfoTypes.Started)
                {
                    taskLog.Add(string.Format("Task '{0}' was started on '{1}' but was not finished. Probably the application was restarted while the task was running.", item.Name, item.LastRunTime), LogPriority.High);
                    item.Status = TaskInfoTypes.Failed;
                    dataService.SaveTaskInfo(item);
                }
            }
        }

        public string GetApplicationPath()
        {
            string appPath = null;
            try
            {
                HttpContext context = HttpContext.Current;
                if (context != null)
                {
                    appPath = string.Format("{0}://{1}{2}{3}",
                      context.Request.Url.Scheme,
                      context.Request.Url.Host,
                      context.Request.Url.Port == 80
                        ? string.Empty : ":" + context.Request.Url.Port,
                      context.Request.ApplicationPath);
                }
                if (!appPath.EndsWith("/"))
                    appPath += "/";
            }
            catch
            {
                return null;
            }
            return appPath;
        }

        private void CheckAttributeForAvailability(XmlAttributeCollection attributes, string name)
        {
            if (attributes[name] == null || string.IsNullOrEmpty(attributes[name].Value))
                throw new ConfigurationErrorsException(string.Format("The '{0}' attribute was not found for a task.", name));
        }

        #endregion
    }

}
